
#include "superpeer.h"

#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <fcntl.h>
#include "structures.h"
#include "mod_file.h"
#include "mod_network.h"
#include "mod_protocol.h"
#include "mod_config.h"
#include "mod_balance.h"
#include <glib-2.0/glib/gtypes.h>
#include <glib-2.0/glib/glist.h>
#include <glib-2.0/glib/gthread.h>

extern PeerConfig *pconf;
extern GList* fileRepresentation;
extern GList* connectedPeer;
extern GList * listaSP;
extern int nRepliche;
//sono di SDriveSP

gpointer handleOperation(gpointer arg) {
    int retVal;
    int* retValPtr = &retVal;
    int operation = -1;
    int error;
    if (readn((int) arg, &operation, sizeof (int)) < 0) {
        retVal = -1;
        return ((gpointer) retValPtr);
    }
    if (operation == 8) {
        printf("ESECUZIONE JOIN sul socket %d\n", (int) arg);
        if ((error = handleJoin((int) arg)) < 0) {
            printf("errore %d nella join\n", error);
            retVal = -2;
            return ((gpointer) retValPtr);
        }
        printf("soket %d chiuso dopo JOIN\n", (int) arg);
        //printf("\n\n\n\nlista file nel sistema\n\n\n");
        //print_FILE_IN_SUPER_PEER_list(fileRepresentation);
        //printf("\n\n\n\nlista peer connessi\n\n\n");
        print_connected_peer(connectedPeer);
        //printf("\n\n");
        retVal = 0;
        return ((gpointer) retValPtr);
    }
    if (operation == 9) {
        printf("ESECUZIONE LEAVE sul socket %d\n", (int) arg);
        if ((error = handleLeave((int) arg)) < 0) {
            printf("errore %d nella leave\n", error);
            retVal = -3;
            return ((gpointer) retValPtr);
        }
        printf("soket %d chiuso dopo LEAVE\n", (int) arg);
        //printf("\n\n\n\nlista file nel sistema\n\n\n");
        //print_FILE_IN_SUPER_PEER_list(fileRepresentation);
        //printf("\n\n\n");
        //printf("\n\n\n\nlista peer connessi\n\n\n");
        //print_connected_peer(connectedPeer);
        //printf("\n\n");
        retVal = 0;
        return ((gpointer) retValPtr);
    }
    // operazione put
    if (operation == 11) {
        printf("ESECUZIONE PUT sul socket %d\n", (int) arg);
        if ((handle_put((int) arg)) < 0) {
            return -4;
        }
        printf("\n\n\n\nlista file nel sistema\n\n\n");
        print_FILE_IN_SUPER_PEER_list(fileRepresentation);
        printf("\n\n\n");
        printf("\n\n\n\nlista peer connessi\n\n\n");
        print_connected_peer(connectedPeer);
        printf("\n\n");

    }
    else {
        printf("operazione sconosciuta\n");
        retVal = -4;
        return ((gpointer) retValPtr);
    }
}

int handleLeave(int connSD) {
    struct PEER *peerToRemove = calloc(1, sizeof (struct PEER));

    if ((readn(connSD, &peerToRemove->port, sizeof (int))) < 0) {
        return -1;
    }
    //ricavo l'indirizzo IP del peer
    struct sockaddr_in* tempAddr = calloc(1, sizeof (struct sockaddr_in));
    socklen_t sl;
    sl = sizeof (*tempAddr);
    if ((getpeername(connSD, (struct sockaddr*) tempAddr, &sl)) < 0) {
        return -2;
    }
    sscanf(inet_ntoa(tempAddr->sin_addr), "%s", peerToRemove->addr);
    if ((removePeer(peerToRemove)) < 0) {
        return -4;
    }
    // chiudo il socket
    if ((close_socket(&connSD)) < 0) {
        return -3;
    }
    free(peerToRemove);
    return 0;
}

//funzione del SUPER_PEER

int request_doReplica(struct chunk * chks, int nChunk, char addr[32], int portSP) {

    struct sockaddr_in addrTemp;
    int sd = create_TCP_client_socket(addr, portSP, &addrTemp);
    connect_with_server(&sd, &addrTemp);
    int op = 2;
    writen(sd, &op, sizeof (int));
    struct SUPER_PEER * me = (struct SUPER_PEER *) g_list_nth_data(listaSP, 0);
    writen(sd, &me->load, sizeof (int));
    writen(sd, &me->ava, sizeof (float));
    writen(sd, &nRepliche, sizeof (int));
    writen(sd, &nChunk, sizeof (int));
    writen(sd, chks, sizeof (struct chunk) * nChunk);

    close_socket(&sd);

    return 1;
}

int handle_doReplica(int *sd) {

    //leggo il carico e la disponibilita' del SUPER_PEER
    //ci vuole anche la porta d'ascolto del SUPER_PEER?

    int load = 0;
    float ava = 0.0;

    readn((*sd), &load, sizeof (int));

    readn((*sd), &ava, sizeof (float));

    int nRepliche = 4;

    readn((*sd), &nRepliche, sizeof (int));

    //a questo punto passo il controllo al bilanciamento di carico fra i peer
    //per controllare se è possibile effettuare tutte le repliche oppure no
    struct peerLight * peerScelti;

    int overCopy = peerLoadBalance(nRepliche, peerScelti);

    if (overCopy == 0) {
        //i peer sono sufficienti per accogliere tutte le repliche

        //i peerScelti sono tanti quanti nRepliche
    } else {
        //c'è necessità di effettuare repliche su altri SP

        //i peer scelti sono tanti quanti i peer presenti
    }

}

int handle_put(int connSD) {
    struct FILE_IN_SUPER_PEER*  newFile= calloc(1, sizeof(struct FILE_IN_SUPER_PEER));
    int numberChunk;
    int peerLoad;
    float peerAva;
    int peerPort;
    char peerIP[INET_ADDRSTRLEN];
    //lettura del carico
    if ((readn(connSD, &peerLoad, sizeof (int))) < 0) {
        return -1;
    }
    //lettura della disponibilità
    if ((readn(connSD, &peerAva, sizeof (float))) < 0) {
        return -2;
    }
    //lettura della porta
    if ((readn(connSD, &peerPort, sizeof (int))) < 0) {
        return -3;
    }
    //ricavo l'indirizzo IP del peer
    struct sockaddr_in* tempAddr = calloc(1, sizeof (struct sockaddr_in));
    socklen_t sl;
    sl = sizeof (*tempAddr);
    if ((getpeername(connSD, (struct sockaddr*) tempAddr, &sl)) < 0) {
        return -4;
    }
    sscanf(inet_ntoa(tempAddr->sin_addr), "%s", peerIP);
    if ((readn(connSD, &newFile->ID, 33)) < 0) {
        return -5;
    }
    if ((readn(connSD, &newFile->name, 100)) < 0) {
        return -6;
    }
    //inizializzazione delle liste
    newFile->chunkInSuperPeer = NULL;
    newFile->completePossessorsPeer = NULL;
    // inizializzazione dei thread
    g_static_rw_lock_init(&newFile->chunkListLock);
    g_static_rw_lock_init(&newFile->completePossessorLock);
    if ((readn(connSD, &numberChunk, sizeof(int))) < 0) {
        return -7;
    }
    if(numberChunk <= 0)
    {
        return -7;
    }
    //eventualmente mettere qui il controllo sulla dimensione
    struct chunk* chunkArray = calloc(numberChunk, sizeof(struct chunk));
    if ((readn(connSD, chunkArray, numberChunk*sizeof(struct chunk))) < 0) {
        return -8;
    }
    if((insertNewFile(newFile, chunkArray, numberChunk, peerLoad, peerAva, peerIP, peerPort))<0){
        return -9;
    }
    //a questo punto andrebbe pesato il carico degli altri peer/SP per distribuire
    //i chunk
    free(chunkArray);
    //invio la conferma sul socket
    int num_op = 0;
    int mex = 0;

    if ((writen(connSD, &num_op, sizeof (int))) < 0) {
        return -10;
    }
    writen(connSD, &num_op, sizeof(int));

    if ((close_socket(&connSD)) < 0) {
        return -11;
    }
    
}

int handleJoin(int connSD) {
    struct PEER *newPeer = calloc(1, sizeof (struct PEER));
    //numero di file interi posseduti dal peer
    int numberCompliteFile = 0;
    char* hashCompleteFiles = NULL;
    //numero di chunk di file non completi posseduti dal peer
    int numberChunk;
    //lettura del carico
    if ((readn(connSD, &newPeer->load, sizeof (int))) < 0) {
        return -3;
    }
    //lettura della disponibilità
    if ((readn(connSD, &newPeer->ava, sizeof (float))) < 0) {
        return -4;
    }
    //lettura della porta
    if ((readn(connSD, &newPeer->port, sizeof (int))) < 0) {
        return -5;
    }
    //ricavo l'indirizzo IP del peer
    struct sockaddr_in* tempAddr = calloc(1, sizeof (struct sockaddr_in));
    socklen_t sl;
    sl = sizeof (*tempAddr);
    if ((getpeername(connSD, (struct sockaddr*) tempAddr, &sl)) < 0) {
        return -6;
    }
    sscanf(inet_ntoa(tempAddr->sin_addr), "%s", newPeer->addr);

    //lettura del numero di file completi
    if ((readn(connSD, &numberCompliteFile, sizeof (int))) < 0) {
        return -7;
    }
    //vettore che contiene gli hash dei file completi
    if (numberCompliteFile > 0) {

        hashCompleteFiles = calloc(32 * numberCompliteFile, sizeof (char));

        //lettura degli hash dei file completi
        if ((readn(connSD, hashCompleteFiles, 32 * numberCompliteFile * sizeof (char))) < 0) {
            return -8;
        }
    }


    //lettura del numero di chunk di file non completi
    if ((readn(connSD, &numberChunk, sizeof (int))) < 0) {
        return -10;
    }
    //vettore di strutture
    struct chunkLight *chunksLite = calloc(numberChunk, sizeof (struct chunkLight));

    //lettura delle strutture
    if ((readn(connSD, chunksLite, numberChunk * sizeof (struct chunkLight))) < 0) {
        return -11;
    }

    if ((insertNewPeer(newPeer, numberCompliteFile, hashCompleteFiles, numberChunk, chunksLite)) < 0) {
        return -12;
    }
    //numero operazione
    int num_op = 0;
    int ack = 1;
    int text_len = 0;
    //scrivo sul socket il numero dell'operazione
    if ((writen(connSD, &num_op, sizeof (int))) < 0) {
        return -13;
    }
    //scrivo sul socket il numero dell'operazione
    if ((writen(connSD, &ack, sizeof (int))) < 0) {
        return -14;
    }
    //scrivo sul socket il numero dell'operazione
    if ((writen(connSD, &text_len, sizeof (int))) < 0) {
        return -15;
    }
    // chiudo il socket
    if ((close_socket(&connSD)) < 0) {
        return -16;
    }
    free(hashCompleteFiles);
    free(chunksLite);
    return 0;
}
//questa funzione viene eseguita dal SUPER_PEER
//invia ad ogni peer rappresentato in peerLight un pacchetto di tipo deleteFile
//con l'hash IDF
//testata e funzionante

int request_delete_file(struct peerLight * pl, int nPeer, char IDF[33]) {

    int i = 0;
    int op = 20;

    struct sockaddr_in addr;

    for (i = 0; i < nPeer; i++) {

        int sd = create_TCP_client_socket(pl->addr, pl->port, &addr);

        connect_with_server(&sd, &addr);

        writen(sd, &op, sizeof(int));

        //scrivo l'hash del file da rimuovere

        writen(sd, IDF, sizeof(char) *32);

        close_socket(&sd);

        pl++;
    }


    return 1;
}
